// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package structured

import (
	"strings"
)

// keyItem represents a node used as a key for merge or patch merge directives.
// It must be hashable as a string value, but also needs to retain its type.
type keyItem struct {
	StringKey    string
	OriginalNode Node
}

// newkeyItemFromScalarNode returns keyItem instance from a scalar node.
// It may returns error when the scalar type is not convertible to a string value.
func newkeyItemFromScalarNode(node Node) (keyItem, error) {
	stringKey, err := getScalarAsString(node)
	if err != nil {
		return keyItem{}, err
	}
	return keyItem{
		StringKey:    stringKey,
		OriginalNode: node,
	}, nil
}

// MergeConfiguration contains configurations of merging a previous node and patch node.
// This configuration is modified throughout walking every nodes during the merging.
type MergeConfiguration struct {
	// MergeMapOrderStrategy decides the order of map keys generated by the merge.
	MergeMapOrderStrategy MergeMapOrderStrategy
	// ArrayMergeConfigResolver resolves array merge strategy of a sequence node at a specific node.
	// Arrays defined in kubernetes manifest can be replaced or merged with using keys. These are different by the field path of the manifest.
	ArrayMergeConfigResolver *MergeConfigResolver

	// patchDirectiveReplace instruct map fields needs to be replaced instead of merge strategy.
	// This field is used for supporting $patch directive in strategic merge patch: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-api-machinery/strategic-merge-patch.md#replace-directive
	patchDirectiveReplace bool
	// patchDirectiuveDelete instruct the map fields must be deleted or not.
	// This field is used for supporting $patch directive in strategic merge patch: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-api-machinery/strategic-merge-patch.md#replace-directive
	patchDirectiveDelete bool
	// deleteFromPrimitiveListDirectiveList is the list of primitive values deleted on the node.
	// This field is used for supporting $deleteFromPrimitiveList directive in strategic merge patch
	deleteFromPrimitiveListDirectiveList map[string]struct{}
	// retainKeysDirectiveList is the list of keys retained on the node.
	// This field is used for supporting $retainKeys directive
	retainKeysDirectiveList map[string]struct{}
	// setElementOrderDirectveList is the ordered list of keys of current array.
	// This field is used for supporting $setElementOrder directive.
	setElementOrderDirectiveList []keyItem

	// These directives are found on the parent map and activated on it's child field.
	// Directive configurations are captured on parsing the parent map and stored in these field once to pass them in `deleteFromPrimitiveListDirectiveList`, `retainKeysDirectiveList` or `setElementOrderDirectiveList`.
	deleteFromPrimitiveListDirectiveListForChildren map[string]map[string]struct{}
	retainKeysDirectiveListForChildren              map[string]map[string]struct{}
	setElementOrderListForChildren                  map[string][]keyItem
}

// GetArrayMergeStrategyAndKey returns the strategy of merging a sequence of maps and the key field name used for merging.
func (c *MergeConfiguration) GetArrayMergeStrategyAndKey(fieldPath []string) (strategy MergeArrayStrategy, mergeKey string, err error) {
	joinedFieldPath := strings.Join(fieldPath[:len(fieldPath)-1], ".") // Remove the last `[]` and construct string represented field path.
	strategy = c.ArrayMergeConfigResolver.GetMergeArrayStrategy(joinedFieldPath)
	if strategy == MergeStrategyMerge {
		mergeKey, err = c.ArrayMergeConfigResolver.GetMergeKey(joinedFieldPath)
		if err != nil {
			return MergeStrategyMerge, "", err
		}
	}
	return
}

type MergeMapOrderStrategy interface {
	// GetMergedKeyOrder returns the order of keys after merging.
	// prevKeys is the keys of previous map.
	// patchKeys is the keys of patch map.
	// directiveKeys is the keys only found in the strategic patch merge directives. These fields are missing in prev and patch, but the existence is inferred from the directives.
	GetMergedKeyOrder(prevKeys []string, patchKeys []string, directiveKeys []string) ([]string, error)
}

type DefaultMergeMapOrderStrategy struct {
}

// GetMergedKeyOrder implements MergeMapOrderStrategy.
func (d *DefaultMergeMapOrderStrategy) GetMergedKeyOrder(prevKeys []string, patchKeys []string, directiveKeys []string) ([]string, error) {
	result := []string{}
	foundInPrev := map[string]struct{}{}
	for _, key := range prevKeys {
		foundInPrev[key] = struct{}{}
		result = append(result, key)
	}
	for _, key := range patchKeys {
		if _, ok := foundInPrev[key]; !ok {
			result = append(result, key)
		}
	}
	for _, key := range directiveKeys {
		if _, ok := foundInPrev[key]; !ok {
			result = append(result, key)
		}
	}
	return result, nil
}
